﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Reflection;


namespace BizElements.BusinessLayer
{
    /// <summary>
    /// Provides common operations which use advanced reflection features.
    /// </summary>
    public static class ReflectionUtil
    {
        /// <summary>Combination of the following flags: Public, NonPublic and Instance.</summary>
        static readonly BindingFlags InstancePublicAndPrivate = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

        /// <summary>Combination of the following flags: Public, NonPublic and Instance.</summary>
        static readonly BindingFlags StaticPublicAndPrivate = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;        

        #region InvokeMethod.

        /// <summary>Invokes a method.</summary>
        /// <param name="obj">Object whose definition (class) contains the specified method.</param>
        /// <param name="methodName">Method to be executed.</param>
        /// <param name="methodParameters">Method parameters.</param>
        /// <returns>Method's return value.</returns>
        public static object InvokeMethod(object obj, string methodName, object[] methodParameters)
        {
            MethodInfo method = FindMethod(obj, methodName, methodParameters);
            if (method == null)
                throw new Exception("Method not found: " + methodName);

            object retval = method.Invoke(obj, methodParameters);
            return retval;
        }

        /// <summary>Tries to find a method.</summary>
        /// <param name="obj">Object whose definition (class) contains the specified method.</param>
        /// <param name="methodName">Method to find.</param>
        /// <param name="methodParameters">Method parameters.</param>
        /// <returns>MethodInfo if method is found; otherwise <b>null</b></returns>
        private static MethodInfo FindMethod(object obj, string methodName, object[] methodParameters)
        {
            MethodInfo method;
            Type[] parameterTypes = TryDetermineParametersType(methodParameters);
            if (parameterTypes == null)
            {
                method = obj.GetType().GetMethod(methodName, InstancePublicAndPrivate);
                if (method.GetParameters().Length != methodParameters.Length)
                    method = null;
            }
            else
            {
                method = obj.GetType().GetMethod(methodName, InstancePublicAndPrivate, null, parameterTypes, null);
            }

            return method;
        }

        /// <summary>Tries to determine method parameters.</summary>
        /// <param name="methodParameters">Method parameters. Types cannot determined if nulls are present in array.</param>
        /// <returns>Array of types if successful; <b>null</b> is any of the provided values is null.</returns>
        private static Type[] TryDetermineParametersType(object[] methodParameters)
        {
            Type[] parameterTypes = new Type[methodParameters.Length];
            for (int paramIdx = 0; paramIdx < methodParameters.Length; paramIdx++)
            {
                if (methodParameters[paramIdx] != null)
                    parameterTypes[paramIdx] = methodParameters[paramIdx].GetType();
                else
                    return null;
            }

            return parameterTypes;
        }

        #endregion

        #region InvokeStaticMethod.

        /// <summary>Invokes a static method.</summary>
        /// <param name="type">Type which contains the specified static method.</param>
        /// <param name="methodName">Method to be executed.</param>
        /// <param name="methodParameters">Method parameters.</param>
        /// <returns>Method's return value.</returns>
        public static object InvokeStaticMethod(Type type, string methodName, object[] methodParameters)
        {
            MethodInfo method = FindStaticMethod(type, methodName, methodParameters);
            if (method == null)
                throw new InvalidOperationException("Method " + methodName + " not found.");

            object retval = method.Invoke(null, methodParameters);
            return retval;
        }

        #endregion

        #region FindStaticMethod.

        /// <summary>Tries to get a static method.</summary>
        /// <param name="type">Type which contains the specified static method.</param>
        /// <param name="methodName">Method to find.</param>
        /// <param name="methodParameters">Method parameters.</param>
        /// <returns>MethodInfo if method is found; otherwise <b>null</b></returns>
        public static MethodInfo FindStaticMethod(Type type, string methodName, object[] methodParameters)
        {
            MethodInfo method;
            Type[] parameterTypes = TryDetermineParametersType(methodParameters);
            if (parameterTypes == null)
                method = type.GetMethod(methodName, StaticPublicAndPrivate);
            else
                method = type.GetMethod(methodName, StaticPublicAndPrivate, null, parameterTypes, null);

            return method;
        }

        #endregion

        #region InvokeGenericMethod.

        /// <summary>Invokes a generic method.</summary>
        /// <param name="obj">Object whose definition (class) contains the specified method.</param>
        /// <param name="methodName">Method to be executed.</param>
        /// <param name="methodParameters">Method parameters.</param>
        /// <returns>Method's return value.</returns>
        public static object InvokeGenericMethod(object obj, string methodName, object[] methodParameters)
        {
            // The best we can do with generic methods is filter by parameter count.
            MethodInfo[] allMethods = obj.GetType().GetMethods(InstancePublicAndPrivate);
            MethodInfo nonGenericMethod;
            int parameterCount = (methodParameters != null) ? methodName.Length : 0;
            if (parameterCount > 0)
                nonGenericMethod = ArrayUtil.Find<MethodInfo>(allMethods, (met) => (met.Name == methodName));
            else
                nonGenericMethod = ArrayUtil.Find<MethodInfo>(allMethods, (met) => (met.Name == methodName) && (met.GetParameters().Length == parameterCount));

            if (nonGenericMethod == null)
                throw new Exception("Method " + methodName + " not found.");

            Type[] parameterTypes = new Type[methodParameters.Length];
            for (int paramIdx = 0; paramIdx < methodParameters.Length; paramIdx++)
            {
                if (methodParameters[paramIdx] != null)
                    parameterTypes[paramIdx] = methodParameters[paramIdx].GetType();
                else
                    throw new InvalidOperationException("Nulls are not supported when invoking generic method " + methodName + ". Parameter at index " + paramIdx + " is null.");
            }

            MethodInfo genericMethod = nonGenericMethod.MakeGenericMethod(parameterTypes);
            object retval = genericMethod.Invoke(obj, methodParameters);
            return retval;
        }

        #endregion

        #region InvokeGenericStaticMethod.

        /// <summary>
        /// Invokes a generic static method.
        /// </summary>
        /// <param name="type">Type which contains the specified static method.</param>
        /// <param name="methodName">Method to be executed.</param>
        /// <param name="methodParameters">Method parameters.</param>
        /// <returns>Method's return value.</returns>
        public static object InvokeGenericStaticMethod(Type type, string methodName, object[] methodParameters)
        {
            // The best we can do with generic methods is filter by parameter count.
            MethodInfo[] allMethods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
            MethodInfo nonGenericMethod;
            int parameterCount = (methodParameters != null) ? methodName.Length : 0;
            if (parameterCount > 0)
                nonGenericMethod = ArrayUtil.Find<MethodInfo>(allMethods, (met) => (met.Name == methodName));
            else
                nonGenericMethod = ArrayUtil.Find<MethodInfo>(allMethods, (met) => (met.Name == methodName) && (met.GetParameters().Length == parameterCount));

            if (nonGenericMethod == null)
                throw new Exception("Method " + methodName + " not found.");

            Type[] parameterTypes = new Type[methodParameters.Length];
            for (int paramIdx = 0; paramIdx < methodParameters.Length; paramIdx++)
            {
                if (methodParameters[paramIdx] != null)
                    parameterTypes[paramIdx] = methodParameters[paramIdx].GetType();
                else
                    throw new InvalidOperationException("Nulls are not supported when invoking generic method " + methodName + ". Parameter at index " + paramIdx + " is null.");
            }

            MethodInfo genericMethod = nonGenericMethod.MakeGenericMethod(parameterTypes);
            object retval = genericMethod.Invoke(null, methodParameters);
            return retval;
        }

        #endregion

        #region CopyObject.

        /// <summary>
        /// Copies entire object graph to another instance of the same type.
        /// </summary>
        /// <param name="source">Source object.</param>
        /// <param name="destination">Destination object which is to receive the data contained in the source object.</param>
        public static void CopyObject(object source, object destination)
        {
            if (source.GetType() != destination.GetType())
                throw new InvalidOperationException("Source and destination objects are not of the same type. Source object type: " + source.GetType().ToString() + ". Destination object type: " + destination.GetType().ToString());

            // Climb the object hierarchy and copy all private, protected and public instance fields.
            Type type = source.GetType();
            while (type != null)
            {
                FieldInfo[] fieldsDeclaredInCurrentType = type.GetFields(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic);
                foreach (FieldInfo field in fieldsDeclaredInCurrentType)
                {
                    object currentFieldValue = field.GetValue(source);
                    field.SetValue(destination, currentFieldValue);
                }

                type = type.BaseType;
            }
        }

        #endregion

        #region CopyPublicProperties.

        /// <summary>
        /// Copies all values of public properties to another instance of the same type.
        /// </summary>
        /// <param name="source">Source object.</param>
        /// <param name="destination">Destination object which is to receive the data contained in the source object.</param>
        /// <remarks>Only properties which have getter and setter are copied.</remarks>
        public static void CopyPublicProperties(object source, object destination)
        {
            if (source.GetType() != destination.GetType())
                throw new Exception("Source and destination objects are not of the same type. Source object type: " + source.GetType().ToString() + ". Destination object type: " + destination.GetType().ToString());

            PropertyInfo[] allProperties = source.GetType().GetProperties();
            foreach (PropertyInfo prop in allProperties)
            {
                bool hasGetterAndSetter = (prop.CanWrite && prop.CanRead);
                if (!hasGetterAndSetter)
                    continue;

                object val = prop.GetValue(source, null);
                prop.SetValue(destination, val, null);
            }
        }

        #endregion

        #region GetPublicPropertyValue.

        /// <summary>
        /// Searches for the public property with the specified name and returns its value.
        /// </summary>
        /// <param name="obj">The object whose property value will be returned.</param>
        /// <param name="propertyName">Name of the public property to get.</param>
        /// <returns>The property value.</returns>
        public static object GetPublicPropertyValue(object obj, string propertyName)
        {
            PropertyInfo prop = obj.GetType().GetProperty(propertyName);
            return prop.GetValue(obj, null);
        }

        #endregion

        #region FindMember.

        const BindingFlags allInstanceMembers = BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic;

        /// <summary>
        /// Tries to find a member in object's class hierarchy.
        /// </summary>
        /// <param name="obj">Object whose type hierarchy is inspected.</param>
        /// <param name="memberName">Name of the field or property to find.</param>
        /// <param name="field">A <see cref="FieldInfo"/> instance if matching field is found; <b>null</b> otherwise.</param>
        /// <param name="property">A <see cref="PropertyInfo"/> instance if matching property is found; <b>null</b> otherwise.</param>
        /// <returns><b>true</b> if member with the specified name was found; <b>false</b> otherwise.</returns>
        public static bool FindMember(object obj, string memberName, out FieldInfo field, out PropertyInfo property)
        {
            field = null;
            property = null;
            Type objType = null;
            bool foundMember = false;
            while (!foundMember)
            {
                objType = (objType == null) ? obj.GetType() : objType.BaseType;
                if (objType == null)
                    break;

                field = objType.GetField(memberName, allInstanceMembers);
                if (field == null)
                    property = objType.GetProperty(memberName, allInstanceMembers);

                foundMember = (field != null || property != null);
            }

            return foundMember;
        }

        #endregion

        #region TrySetValueAtPath.

        static readonly IMessageFormatter DefaultFormatter = new MessageFormatter();

        /// <summary>
        /// Tries to set the value at the specified path in the object graph.
        /// </summary>
        /// <param name="component">Component/object whose object graph is analyzed and whose sub-component is to receive the value.</param>
        /// <param name="path">Array of public property names and indexer arguments.</param>
        /// <param name="value">Value to set in the final sub-component of the path.</param>
        /// <param name="logErrorsAsWarnings">Whether to log all encountered errors are logged as warnings (see <see cref="Log.AppWarning"/>).</param>
        /// <returns><b>true</b> if the operation was successfull; <b>false</b> otherwise.</returns>
        public static bool TrySetValueAtPath(object component, string[] path, object value, bool logErrorsAsWarnings)
        {
            // Imaginary example:
            // this.btn.Style["display"].Value = "none";
            // Style/display/Value
            // Get Style component
            // Get display component
            // Write to Value property

            object subComponent = component;
            int idxSegment = 0;
            while (idxSegment < path.Length)
            {
                string[] unprocessedPath = new string[path.Length - idxSegment];
                Array.Copy(path, idxSegment, unprocessedPath, 0, unprocessedPath.Length);

                bool subComponentReceivesTheValue;
                if (idxSegment == path.Length - 1)
                {
                    // End of path. We expect that the component represents a property or a single argument indexer.
                    subComponentReceivesTheValue = true;
                }
                else
                {
                    // Check if we have reached an indexer whose parameters can be parsed from the path tail.
                    PropertyInfo indexer = FindMatchingIndexer(subComponent, unprocessedPath);
                    subComponentReceivesTheValue = (indexer != null);
                }

                if (subComponentReceivesTheValue)
                {
                    // Write and exit.                    
                    bool isSuccess = WriteToPropertyOrIndexer(subComponent, unprocessedPath, value, logErrorsAsWarnings);
                    return isSuccess;
                }
                else
                {
                    int usedPathSegmentCount;
                    subComponent = GetSubComponent(subComponent, unprocessedPath, out usedPathSegmentCount, logErrorsAsWarnings);
                    idxSegment += usedPathSegmentCount;
                }

                bool pathDoesntMatchObjectGraph = (subComponent == null);
                if (pathDoesntMatchObjectGraph)
                {
                    if (logErrorsAsWarnings)
                    {
                        Dictionary<string, object> errDetails = new Dictionary<string, object>();
                        errDetails.Add("Component type", component.GetType());
                        errDetails.Add("Path", GetPathString(path));
                        errDetails.Add("Value", value);
                        Log.AppWarning("ReflectionUtil.SetValueAtPath", "Path doesn't match component's object graph.", errDetails);
                    }

                    return false;
                }
            }

            return false;
        }

        #region FindMatchingIndexer.

        static Dictionary<Type, object> typesWithMultipleIndexers = new Dictionary<Type, object>();

        private static PropertyInfo FindMatchingIndexer(object obj, string[] txtIndexerArgs)
        {
            if (typesWithMultipleIndexers.ContainsKey(obj.GetType()))
                return FindMatchingIndexerInTypeWithMultipleIndexers(obj, txtIndexerArgs);
            else
                return FindMatchingIndexerInTypeWithSingleIndexer(obj, txtIndexerArgs);
        }

        private static PropertyInfo FindMatchingIndexerInTypeWithSingleIndexer(object obj, string[] txtIndexerArgs)
        {
            PropertyInfo indexer = null;
            try
            {
                PropertyInfo candidate = obj.GetType().GetProperty("Item");
                bool isMatch = (candidate != null) && (indexer.GetIndexParameters().Length == txtIndexerArgs.Length);
                if (isMatch)
                    indexer = candidate;
            }
            catch (AmbiguousMatchException)
            {
                typesWithMultipleIndexers[obj.GetType()] = null;
                indexer = FindMatchingIndexerInTypeWithMultipleIndexers(obj, txtIndexerArgs);
            }

            return indexer;
        }

        const string Item = "Item";

        private static PropertyInfo FindMatchingIndexerInTypeWithMultipleIndexers(object obj, string[] txtIndexerArgs)
        {
            PropertyInfo indexer = null;
            foreach (PropertyInfo prop in obj.GetType().GetProperties())
            {
                bool isIndexer = (prop.Name == Item);
                if (!isIndexer)
                    continue;

                ParameterInfo[] indexerParams = prop.GetIndexParameters();
                bool matchesArgCount = (indexerParams.Length == txtIndexerArgs.Length);
                bool matchesArgTypes;
                if (matchesArgCount)
                {
                    bool nonMatchingArgFound = false;
                    int argIndex = 0;
                    while (!nonMatchingArgFound && argIndex < indexerParams.Length)
                    {
                        Type argType = indexerParams[argIndex].ParameterType;
                        string pathSegmenet = txtIndexerArgs[argIndex];
                        object argValue = NumberParser.ParseValue(argType, pathSegmenet, DefaultFormatter);
                        if (argValue == null)
                            nonMatchingArgFound = true;

                        argIndex++;
                    }

                    matchesArgTypes = !nonMatchingArgFound;
                }
                else
                {
                    matchesArgTypes = false;
                }

                if (matchesArgTypes)
                {
                    indexer = prop;
                    break;
                }
            }

            return indexer;
        }

        #endregion

        private static string GetPathString(string[] path)
        {
            string pathString = "";
            foreach (string segment in path)
                pathString += segment + "/";

            if (pathString.Length > 0)
                pathString = pathString.Substring(0, pathString.Length - 1);

            return pathString;
        }

        private static object GetSubComponent(object parentObject, string[] pathTail, out int usedPathSegmentCount, bool logErrorsAsWarnings)
        {
            string propertyOrIndex = pathTail[0];
            object subComponent = null;
            usedPathSegmentCount = 0;

            Type parentType = parentObject.GetType();
            PropertyInfo publicProp = parentType.GetProperty(propertyOrIndex);
            if (publicProp != null)
            {
                subComponent = publicProp.GetValue(parentObject, null);
                usedPathSegmentCount = 1;
            }
            else
            {
                // Find an indexer whose parameters can be parsed by consuming some or all of 
                // the segments in the path tail. Prefer indexers with less parameters.
                PropertyInfo indexer = null;
                for (int argsCount = 1; argsCount <= pathTail.Length; argsCount++)
                {
                    string[] args = new string[argsCount];
                    Array.Copy(pathTail, args, argsCount);
                    indexer = FindMatchingIndexer(parentObject, args);
                    if (indexer != null)
                        break;
                }

                ParameterInfo[] indexParameters = (indexer != null) ? indexer.GetIndexParameters() : null;
                bool isIndexerAndPathContainsAllArgs = (indexParameters != null) && (indexParameters.Length > 0) && (indexParameters.Length <= pathTail.Length);
                if (isIndexerAndPathContainsAllArgs)
                {
                    try
                    {
                        object[] index = new object[indexParameters.Length];
                        for (int idxArgument = 0; idxArgument < indexParameters.Length; idxArgument++)
                            index[idxArgument] = Convert.ChangeType(pathTail[idxArgument], indexParameters[idxArgument].ParameterType);

                        subComponent = indexer.GetValue(parentObject, index);
                        usedPathSegmentCount = indexParameters.Length;
                    }
                    catch (Exception e)
                    {
                        if (logErrorsAsWarnings)
                        {
                            Dictionary<string, object> errDetails = new Dictionary<string, object>();
                            for (int idxArgument = 0; idxArgument < indexParameters.Length; idxArgument++)
                                errDetails.Add("Indexer argument #" + (1 + idxArgument), pathTail[idxArgument]);

                            Log.AppWarning("ReflectionUtil.GetSubComponent", e.ToString(), errDetails);
                        }
                    }
                }
            }

            return subComponent;
        }

        private static bool WriteToPropertyOrIndexer(object parentObject, string[] pathTail, object value, bool logErrorsAsWarnings)
        {
            bool isSuccess = false;
            string propertyOrIndex = pathTail[0];
            Type parentType = parentObject.GetType();
            PropertyInfo publicProp = parentType.GetProperty(propertyOrIndex);
            if (publicProp != null)
            {
                isSuccess = TryWriteToProperty(parentObject, publicProp, value, logErrorsAsWarnings);
            }
            else
            {
                PropertyInfo indexer = parentType.GetProperty("Item");
                if (indexer != null)
                    isSuccess = TryWriteToIndexer(parentObject, indexer, value, pathTail, logErrorsAsWarnings);
            }

            return isSuccess;
        }

        private static bool TryWriteToProperty(object parentObject, PropertyInfo publicProp, object value, bool logErrorsAsWarnings)
        {
            bool isSuccess = false;
            if (publicProp.CanWrite)
            {
                try
                {
                    object compatibileValue = ConvertValue(publicProp.PropertyType, value);
                    publicProp.SetValue(parentObject, compatibileValue, null);
                    isSuccess = true;
                }
                catch (Exception e)
                {
                    if (logErrorsAsWarnings)
                    {
                        Dictionary<string, object> errDetails = new Dictionary<string, object>();
                        errDetails.Add("Property", publicProp.Name);
                        errDetails.Add("Value", value);
                        Log.AppWarning("ReflectionUtil.WriteToPropertyOrIndexer", e.ToString(), errDetails);
                    }
                }
            }

            return isSuccess;
        }

        private static bool TryWriteToIndexer(object parentObject, PropertyInfo indexer, object value, string[] pathTail, bool logErrorsAsWarnings)
        {
            bool isSuccess = false;
            ParameterInfo[] indexParameters = indexer.GetIndexParameters();
            bool isIndexerAndPathContainsAllArgs = (indexParameters != null) && (indexParameters.Length > 0) && (indexParameters.Length <= pathTail.Length);
            if (isIndexerAndPathContainsAllArgs)
            {
                try
                {
                    object compatibileValue = ConvertValue(indexer.PropertyType, value);
                    object[] index = new object[indexParameters.Length];
                    for (int idxArgument = 0; idxArgument < indexParameters.Length; idxArgument++)
                        index[idxArgument] = Convert.ChangeType(pathTail[idxArgument], indexParameters[idxArgument].ParameterType);

                    indexer.SetValue(parentObject, compatibileValue, index);
                    isSuccess = true;
                }
                catch (Exception e)
                {
                    if (logErrorsAsWarnings)
                    {
                        Dictionary<string, object> errDetails = new Dictionary<string, object>();
                        for (int idxArgument = 0; idxArgument < indexParameters.Length; idxArgument++)
                            errDetails.Add("Indexer argument #" + (1 + idxArgument), pathTail[idxArgument]);

                        Log.AppWarning("ReflectionUtil.GetSubComponent", e.ToString(), errDetails);
                    }
                }
            }

            return isSuccess;
        }

        #endregion

        #region ConvertValue.

        private static object ConvertValue(Type targetType, object value)
        {
            object compatibileValue;
            Type valueType = Nullable.GetUnderlyingType(targetType);
            bool isNullableValueType = (valueType != null);
            if (isNullableValueType)
                compatibileValue = (value != null) ? Convert.ChangeType(value, valueType, CultureInfo.InvariantCulture) : null;
            else
                compatibileValue = Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture);

            return compatibileValue;
        }

        #endregion

        #region TrySetPropertyValue.

        /// <summary>
        /// Tries to set the value in the specified public property.
        /// </summary>
        /// <param name="obj">Object to receive the value through public property.</param>
        /// <param name="publicPropertyName">Writeable public property.</param>
        /// <param name="convertibileValue">A value which can be converted to the type which property accepts.</param>
        /// <param name="logErrorsAsWarnings">Whether to log all encountered errors are logged as warnings (see <see cref="Log.AppWarning"/>).</param>
        /// <returns><b>true</b> if the operation was successfull; <b>false</b> otherwise.</returns>
        public static bool TrySetPropertyValue(object obj, string publicPropertyName, object convertibileValue, bool logErrorsAsWarnings)
        {
            bool isSuccess = false;
            PropertyInfo property = obj.GetType().GetProperty(publicPropertyName);
            if (property != null && property.CanWrite)
            {
                try
                {
                    object value = ConvertValue(property.PropertyType, convertibileValue);
                    property.SetValue(obj, value, null);
                    isSuccess = true;
                }
                catch (Exception e)
                {
                    if (logErrorsAsWarnings)
                    {
                        Dictionary<string, object> errorDetails = new Dictionary<string, object>();
                        errorDetails.Add("Property", publicPropertyName);
                        errorDetails.Add("Value", convertibileValue);
                        Log.AppWarning("ReflectionUtil.TrySetPropertyValue", e.ToString(), errorDetails);
                    }
                }
            }

            return isSuccess;
        }

        #endregion

        #region TryCreateInstance.

        /// <summary>
        /// Tries to locate the specified class in the given assembly and create an instance of it using system activator.
        /// </summary>
        /// <param name="assemblyFullName">The long form of the assembly name.</param>
        /// <param name="classFullName">The <see cref="Type.FullName"/> of the class to locate.</param>
        /// <returns>An instance of <see cref="Object"/> representing the type. <b>null</b> reference if en error occures.</returns>
        /// <remarks>Logs <see cref="Log.AppWarning"/> if an <see cref="Exception"/> occures.</remarks>
        public static object TryCreateInstance(string assemblyFullName, string classFullName)
        {
            object obj;
            if (string.IsNullOrEmpty(assemblyFullName) && string.IsNullOrEmpty(classFullName))
            {
                obj = null;
            }
            else
            {
                try
                {
                    Assembly asm = Assembly.Load(assemblyFullName);
                    obj = asm.CreateInstance(classFullName);
                }
                catch (Exception e)
                {
                    Log.AppWarning("ReflectionUtil.TryCreateInstance", e.ToString(), null);
                    obj = null;
                }
            }

            return obj;
        }

        /// <summary>
        /// Tries to locate the specified class in the given assembly and create an instance of it using system activator.
        /// </summary>
        /// <param name="assemblyStringSetting">Application setting which contains the short or long form of the assembly name.</param>
        /// <param name="classNameSetting">Application setting which contains the full name of the type.</param>
        /// <returns>An instance of <see cref="Object"/> representing the type. <b>null</b> reference if en error occures.</returns>
        /// <remarks>Logs <see cref="Log.AppWarning"/> if an <see cref="Exception"/> occures.</remarks>
        public static object TryCreateInstanceFromAppSettings(string assemblyStringSetting, string classNameSetting)
        {
            if (string.IsNullOrEmpty(assemblyStringSetting)) throw new ArgumentException("Assembly string settings key is required.");
            if (string.IsNullOrEmpty(classNameSetting)) throw new ArgumentException("Class name settings key is required.");

            string cfgAssembly = ConfigurationManager.AppSettings[assemblyStringSetting];
            string cfgClass = ConfigurationManager.AppSettings[classNameSetting];
            return TryCreateInstance(cfgAssembly, cfgClass);
        }

        #endregion

        #region CreateDelegate.

        /// <summary>
        /// Creates a delegate of the specified type to represent the specified static method.
        /// </summary>
        /// <typeparam name="T">Delegate type/signature.</typeparam>
        /// <param name="assemblyString">The short or long form of the assembly name.</param>
        /// <param name="className">The full name of the type.</param>
        /// <param name="methodName">The name of the static method to which a delegate is created.</param>
        /// <returns>Delegate.</returns>
        public static T CreateDelegate<T>(string assemblyString, string className, string methodName)
            where T : class
        {
            if (string.IsNullOrEmpty(assemblyString)) throw new ArgumentException("Assembly string is required.");
            if (string.IsNullOrEmpty(className)) throw new ArgumentException("Class name is required.");
            if (string.IsNullOrEmpty(methodName)) throw new ArgumentException("Method name is required.");

            Assembly asm = Assembly.Load(assemblyString);
            if (asm == null) throw new InvalidOperationException("Cannot load assembly: " + assemblyString);
            Type cls = asm.GetType(className, true);

            MethodInfo method = cls.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
            if (method == null)
                throw new InvalidOperationException("The class '" + className + "' does not contain a static method '" + methodName + "'.");

            T del = Delegate.CreateDelegate(typeof(T), method) as T;
            if (del == null)
                throw new InvalidOperationException("Cannot create delegate.");

            return del;
        }

        /// <summary>
        /// Creates a delegate of the specified type to represent the static method specified in the configuration file.
        /// </summary>
        /// <typeparam name="T">Delegate type/signature.</typeparam>
        /// <param name="assemblyStringSetting">Application setting which contains the short or long form of the assembly name.</param>
        /// <param name="classNameSetting">Application setting which contains the full name of the type.</param>
        /// <param name="methodNameSetting">Application setting which contains the name of the static method to which a delegate is created.</param>
        /// <returns>Delegate.</returns>
        public static T CreateDelegateFromAppSettings<T>(string assemblyStringSetting, string classNameSetting, string methodNameSetting)
            where T : class
        {
            if (string.IsNullOrEmpty(assemblyStringSetting)) throw new ArgumentException("Assembly string settings key is required.");
            if (string.IsNullOrEmpty(classNameSetting)) throw new ArgumentException("Class name settings key is required.");
            if (string.IsNullOrEmpty(methodNameSetting)) throw new ArgumentException("Method name settings key is required.");

            string cfgAssembly = ConfigurationManager.AppSettings[assemblyStringSetting];
            string cfgClass = ConfigurationManager.AppSettings[classNameSetting];
            string cfgMethod = ConfigurationManager.AppSettings[methodNameSetting];
            return CreateDelegate<T>(cfgAssembly, cfgClass, cfgMethod);
        }

        /// <summary>
        /// Tries to creates a delegate of the specified type to represent the static method specified in the configuration file.
        /// </summary>
        /// <typeparam name="T">Delegate type/signature.</typeparam>
        /// <param name="assemblyStringSetting">Application setting which contains the short or long form of the assembly name.</param>
        /// <param name="classNameSetting">Application setting which contains the full name of the type.</param>
        /// <param name="methodNameSetting">Application setting which contains the name of the static method to which a delegate is created.</param>
        /// <returns>Delegate. <b>null</b> if settings are not configured, or if an error occures.</returns>
        /// <remarks>Logs <see cref="Log.AppWarning"/> if an <see cref="Exception"/> occures.</remarks>
        public static T TryCreateDelegateFromAppSettings<T>(string assemblyStringSetting, string classNameSetting, string methodNameSetting)
            where T : class
        {
            if (string.IsNullOrEmpty(assemblyStringSetting)) throw new ArgumentException("Assembly string settings key is required.");
            if (string.IsNullOrEmpty(classNameSetting)) throw new ArgumentException("Class name settings key is required.");
            if (string.IsNullOrEmpty(methodNameSetting)) throw new ArgumentException("Method name settings key is required.");

            string cfgAssembly = ConfigurationManager.AppSettings[assemblyStringSetting];
            string cfgClass = ConfigurationManager.AppSettings[classNameSetting];
            string cfgMethod = ConfigurationManager.AppSettings[methodNameSetting];
            if (string.IsNullOrEmpty(cfgAssembly) || string.IsNullOrEmpty(cfgClass) || string.IsNullOrEmpty(cfgMethod))
                return null;

            try
            {
                return CreateDelegate<T>(cfgAssembly, cfgClass, cfgMethod);
            }
            catch (Exception e)
            {
                Log.AppWarning("ReflectionUtil.TryCreateDelegateFromAppSettings", e.ToString(), null);
                return null;
            }
        }

        /// <summary>
        /// Tries to create a delegate of the specified type to represent the specified static method.
        /// </summary>
        /// <typeparam name="T">Delegate type/signature.</typeparam>
        /// <param name="assemblyString">The short or long form of the assembly name.</param>
        /// <param name="className">The full name of the type.</param>
        /// <param name="methodName">The name of the static method to which a delegate is created.</param>
        /// <returns>Delegate. <b>null</b> if settings are not configured, or if an error occures.</returns>
        /// <remarks>Logs <see cref="Log.AppWarning"/> if an <see cref="Exception"/> occures.</remarks>
        public static T TryCreateDelegate<T>(string assemblyString, string className, string methodName)
            where T : class
        {
            if (string.IsNullOrEmpty(assemblyString) || string.IsNullOrEmpty(className) || string.IsNullOrEmpty(methodName))
                return null;

            try
            {
                return CreateDelegate<T>(assemblyString, className, methodName);
            }
            catch (Exception e)
            {
                Log.AppWarning("ReflectionUtil.TryCreateDelegateFromAppSettings", e.ToString(), null);
                return null;
            }
        }

        #endregion
    }
}